#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <lame/lame.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

enum {
	A_NULL,
	A_RATE,
	A_CHAN,
	A_ENC,
	A_QUALITY
};

struct afile {
	lame_t		 lame;
	void		*buf;
	size_t		 bsize;
	uint32_t	 enc;
	int		 fd;
	int		 first;
};

const struct a_lookup stab[] = {
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ "quality", A_QUALITY },
	{ NULL, A_NULL }
};

static void pcm2int_16(void *, size_t);
static void pcm2int_32(void *, size_t);

struct afile *
af_open(const char *file) {
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_WRONLY)) < 0)
		return (NULL);
	return (af_fdopen(fd));
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;

	if (fd < 0)
		ERR(EBADF, NULL);
	if ((a = malloc(sizeof(struct afile))) == NULL)
		return (NULL);
	if ((a->lame = lame_init()) == NULL) {
		free(a);
		return (NULL);
	}
	a->buf = NULL;
	a->bsize = 0;
	a->enc = AU_PCM16;
	a->fd = fd;
	a->first = 1;
	return (a);
}

void
af_close(struct afile *a)
{
	int r;

	if (a == NULL)
		return;
	if (!a->first)
		if ((r = lame_encode_flush(a->lame, a->buf, a->bsize))
		    > 0)
			(void)nwrite(a->fd, a->buf, r);
	(void)lame_close(a->lame);
	(void)close(a->fd);
	if (a->buf != NULL)
		free(a->buf);
	free(a);
}

ssize_t
af_write(struct afile *a, void *b, size_t size)
{
	size_t samples;
	size_t ssize;
	size_t tsize;
	int r;

	if (a == NULL || b == NULL)
		ERR(EINVAL, -1);
	if (a->first) {
		if (lame_init_params(a->lame) == -1)
			return (-1);
		a->first = 0;
	}
	if (au_encnswap(a->enc, b, size) != 0)
		return (-1);
	ssize = au_encsize(a->enc) * lame_get_num_channels(a->lame);
	samples = size / ssize;
	tsize = (samples * 5 + 3) / 4 + 7200;
	if (a->bsize < tsize) {
		if ((a->buf = a->buf == NULL ? malloc(tsize) :
		    realloc(a->buf, tsize)) == NULL)
			return (-1);
		a->bsize = tsize;
	}
	switch (a->enc) {
	case AU_PCM16:
		pcm2int_16(b, size);
		if ((r = lame_encode_buffer_interleaved(a->lame, b,
		    samples, a->buf, a->bsize)) < 0)
			return (-1);
		break;
	case AU_PCM32:
		pcm2int_32(b, size);
		if ((r = lame_encode_buffer_interleaved_int(a->lame, b,
		    samples, a->buf, a->bsize)) < 0)
			return (-1);
		break;
	case AU_FLOAT32:
		if ((r = lame_encode_buffer_interleaved_ieee_float(
		    a->lame, b, samples, a->buf, a->bsize)) < 0)
			return (-1);
		break;
	case AU_FLOAT64:
		if ((r = lame_encode_buffer_interleaved_ieee_double(
		    a->lame, b, samples, a->buf, a->bsize)) < 0)
			return (-1);
		break;
	default:
		abort();
	}
	if (r > 0 && nwrite(a->fd, a->buf, r) < 0)
		return (-1);
	return (size - size % ssize);
}

int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_RATE:
			*va_arg(ap, uint32_t *) =
			   lame_get_in_samplerate(a->lame);
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) =
			   lame_get_num_channels(a->lame);
			break;
		case A_ENC:
			*va_arg(ap, uint32_t *) = a->enc;
			break;
		case A_QUALITY:
			*va_arg(ap, double *) = 1 -
			    (double)lame_get_quality(a->lame) / 9;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{
	va_list ap;
	double d;
	uint32_t u;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	if (!a->first)
		ERR(EOPNOTSUPP, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_RATE:
			if ((u = va_arg(ap, uint32_t)) > INT_MAX)
				goto err;
			if (lame_set_in_samplerate(a->lame, u) != 0)
				goto err;
			break;
		case A_CHAN:
			if ((u = va_arg(ap, uint32_t)) > INT_MAX)
				goto err;
			if (lame_set_num_channels(a->lame, u) != 0)
				goto err;
			break;
		case A_ENC:
			switch (u = va_arg(ap, uint32_t)) {
			case AU_PCM16:
				break;
			case AU_PCM32:
				break;
			case AU_FLOAT32:
				break;
			case AU_FLOAT64:
				break;
			default:
				goto err;
			}
			a->enc = u;
			break;
		case A_QUALITY:
			if ((d = va_arg(ap, double)) < 0 || d > 1)
				goto err;
			if (lame_set_quality(a->lame, (1 - d) * 9) != 0)
				goto err;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

void
pcm2int_16(void *b, size_t s)
{
	uint16_t *u16 = b;
	int16_t *i16 = b;
	uint32_t u;
	size_t i;

	assert(b != NULL);
	s /= sizeof(int16_t);
	for (i = 0, s /= sizeof(int16_t); i < s; i++)
		if ((u = u16[i]) >> 31)
			i16[i] = (u = ~u + 1) > -(int32_t)INT16_MIN ?
			    INT16_MIN : -(int16_t)u;
		else
			i16[i] = u;
}

void
pcm2int_32(void *b, size_t s)
{
	uint32_t *u32 = b;
	int32_t *i32 = b;
	uint32_t u;
	size_t i;

	assert(b != NULL);
	s /= sizeof(int32_t);
	for (i = 0, s /= sizeof(int32_t); i < s; i++)
		if ((u = u32[i]) >> 31)
			i32[i] = (u = ~u + 1) > -(int64_t)INT32_MIN ?
			    INT32_MIN : -(int32_t)u;
		else
			i32[i] = u;
}
